package model;

import java.util.ArrayList;
import org.apache.log4j.Logger;
import model.Ebtables.Action;
import model.Ebtables.Chain;
import model.Ebtables.Table;
import model.Ebtables.Target;
import model.Tc.Command;
import model.Tc.Element;
import model.Tc.Hierarchy;
import model.exception.TcException;

/**
 * L'oggetto Hub rappresenta il componente principale per la connessione di VM, sia per
 * topologie punto-punto che per quelle a stella. Ad un Hub è possibile collegare uno o più
 * {@link Link} che a loro volta sono connessi ad una {@link NetworkInterface} e quindi
 * ad una VM ({@link Node}). 
 * 
 * @author Dumella De Rosa Gennaro
 * 
 */
public class Hub extends Device {
	
	public static enum CommandType{add,del,update,replace};
	
	private static final Logger log = Logger.getLogger(Hub.class);
	
	private ArrayList<Link> links;
	
	
	/**
	 * Le Property associate a questo link 
	 */
	private Property property;	
	
	/** 
	 * La Bandwidth associata a questo link 
	 */
	private Bandwidth bandwidth;	
	
	
	
	public Hub(String name) {
		super(name);
		links = new ArrayList<Link>();
		property = null;
		bandwidth = null;
	}
	
	ArrayList<Link> getLinks() {
		return links;
	}
	
	public void addLink( Link link ) {
		links.add( link );
	}
	
	public void removeLink( Link link ) {
		links.remove( link );
	}
	
	/**
	 * Restituisce la lista delle schede di rete (un ArrayList di {@link NetworkInterface}) 
	 * connesse, tramite i {@link Link}, all'Hub.
	 * 
	 * @return {@link ArrayList<{@link NetworkInterface}>
	 */
	public ArrayList<NetworkInterface> getInterfaces() 
	{
		ArrayList<NetworkInterface> interfaces = new ArrayList<NetworkInterface>();
		
		for (Link i:links){
			interfaces.add(i.getSource());
		}
		
		return interfaces;
	}
	
	/**
	 * Restituisce la lista (senza duplicati) dei nodi Fisici (un ArrayList di {@link PhysicalNode})
	 * sui quali sono istanziate le VM con almeno una scheda di rete virtuale connessa, 
	 * tramite un link, all'hub.
	 * 
	 * @return {@link ArrayList<{@link Node}>
	 */
	public ArrayList<Node> getPhysicalNode()
	{
		ArrayList<Node> physicalNodes = new ArrayList<Node>();
		
		for (Link i:links){

			Node tmp = i.getSource().getParent().getHoster();
			if ( physicalNodes.indexOf(tmp) == -1 )
				physicalNodes.add(tmp);
			
		}
		return physicalNodes;
	}
	
//###########################################################################################
//########################################    EBTABLES   ####################################
//###########################################################################################
	
	/**
	 * E' il metodo principale per la creazione delle regole ebtables associate alla topologia
	 * determinata dall'Hub. Le regole vengono create ed associate al nodo fisico sul quale
	 * dovranno essere applicate. La relazione <regola,nodo fisico> è mantenuta dagli oggetti
	 * {@link DistributedCommand}. Un ArrayList di DistributedCommand viene quindi restituito dal
	 * metodo. 
	 * 
	 * @return {@link ArrayList<{@link Node}>
	 */
	public ArrayList<DistributedCommand> getEbtablesAddRules(){
		
		ArrayList<DistributedCommand> distributedCommands = new ArrayList<DistributedCommand>();
		ArrayList<NetworkInterface> interfaces = this.getInterfaces();
		
		ArrayList<Node> physicalNodes = this.getPhysicalNode();
		String command = "";
		String MACs = "";
			
		for (int i=0; i < interfaces.size(); i++) {
			command = "";
			MACs = "";

			for (int j=0; j < interfaces.size(); j++) {
				if (j != i)
					MACs += interfaces.get(j).getMacAddress().toString() + ",";			
			}
						
			/* 
			 * Regola per connessione Punto-Punto
			 * ebtables -t filter -A FORWARD -i ... -s ... --among-dst ...,... -j ACCEPT
			 */			
			command = Ebtables.toString(Table.filter, Action.A, Chain.FORWARD,
					interfaces.get(i).getName(),null, interfaces.get(i).getMacAddress(),null,
					MACs.substring(0,MACs.length()-1), false,null, false, 
					Target.ACCEPT);
			
			distributedCommands.add(new DistributedCommand(command, 
										interfaces.get(i).getParent().getHoster()));

			
			/* 
			 * Regola per filtro pacchetti Multicast 
			 * ebtables -t nat -A POSTROUTING -o ... --among-src ! ...,... -j DROP 
			 */
			command = Ebtables.toString(Table.nat, Action.A, Chain.POSTROUTING, 
					null,interfaces.get(i).getName(),  null, null,
					null, false, MACs.substring(0,MACs.length()-1), true, 
					Target.DROP);	
		
			distributedCommands.add(new DistributedCommand(command, 
										interfaces.get(i).getParent().getHoster()));
			
			for (Node node : physicalNodes){
				if( !node.equals(interfaces.get(i).getParent().getHoster() ) )
				{
					/* 
					 * Regola per connessione Punto-Punto su ALTRI server
					 * ebtables -t filter -A FORWARD -s ... --among-dst ...,... -j ACCEPT
					 */
					command = Ebtables.toString(Table.filter, Action.A, Chain.FORWARD, 
							null,null,  interfaces.get(i).getMacAddress(), null,
							MACs.substring(0,MACs.length()-1), false,null, false, 
							Target.ACCEPT);

					distributedCommands.add(new DistributedCommand(command, node));
				}	
			}
			
		}
		return distributedCommands;
	}
	
	/**
	 * Restituisce le regole ebtables che servono ad eliminare quelle create tramite il metodo
	 * {@link Hub#getEbtablesAddRules()} associare alla topologia determinata dall'hub. E' quindi
	 * il metodo complementare a {@link Hub#getEbtablesAddRules()}.
	 * Le regole vengono create ed associate al nodo fisico sul quale dovranno essere applicate. 
	 * La relazione <regola,nodo fisico> è mantenuta dagli oggetti {@link DistributedCommand}. 
	 * Un ArrayList di DistributedCommand viene quindi restituito dal 	 * metodo. 
	 * 
	 * @return {@link ArrayList<{@link Node}>
	 */
	public ArrayList<DistributedCommand> getEbtablesDeleteRules(){
		
		ArrayList<DistributedCommand> distributedCommands = new ArrayList<DistributedCommand>();
		ArrayList<NetworkInterface> interfaces = this.getInterfaces();
		
		ArrayList<Node> physicalNodes = this.getPhysicalNode();
		String command = "";
			
		for (int i=0; i < interfaces.size(); i++) {
			command = "";
			String MACs = "";

			for (int j=0; j < interfaces.size(); j++) {
				if (j != i)
					MACs += interfaces.get(j).getMacAddress().toString() + ",";			
			}
			
			/* 
			 * Regola di eliminazione per connessione Punto-Punto
			 * ebtables -t filter -D FORWARD -i ... -s ... --among-dst ...,... -j ACCEPT
			 */			
			command = Ebtables.toString(Table.filter, Action.D, Chain.FORWARD,
					interfaces.get(i).getName(),null, interfaces.get(i).getMacAddress(),null,
					MACs.substring(0,MACs.length()-1), false,null, false, 
					Target.ACCEPT);
			
			distributedCommands.add(new DistributedCommand(command, 
										interfaces.get(i).getParent().getHoster()));
			
			/* 
			 * Regola di eliminazione per filtro pacchetti Multicast 
			 * ebtables -t nat -D POSTROUTING -o ... -s ... --among-src ! ...,... -j DROP 
			 */
			command = Ebtables.toString(Table.nat, Action.D, Chain.POSTROUTING, 
					null,interfaces.get(i).getName(),  interfaces.get(i).getMacAddress(), null,
					null, false, MACs.substring(0,MACs.length()-1), true, 
					Target.DROP);	
		
			distributedCommands.add(new DistributedCommand(command, 
										interfaces.get(i).getParent().getHoster()));
			
			for (Node n : physicalNodes){
				if( !n.equals(interfaces.get(i).getParent().getHoster() ) )
				{
					/* 
					 * Regola per connessione Punto-Punto su ALTRI server
					 * ebtables -t filter -D FORWARD -s ... --among-dst ...,... -j ACCEPT
					 */
					command = Ebtables.toString(Table.filter, Action.D, Chain.FORWARD, 
							null,null,  interfaces.get(i).getMacAddress(), null,
							MACs.substring(0,MACs.length()-1), false,null, false, 
							Target.ACCEPT);
					
					distributedCommands.add(new DistributedCommand(command, n));
				}	
			}
			
		}
		return distributedCommands;
		
	}
	
//###########################################################################################
//########################################TRAFFIC CONTROL####################################
//###########################################################################################
	
	public ArrayList<DistributedCommand> getTrafficControlRules(CommandType type) throws TcException{
		
		ArrayList<DistributedCommand> distributedCommands = new ArrayList<DistributedCommand>();
		ArrayList<NetworkInterface> interfaces = this.getInterfaces();
		
		String command = "";
		
		for (int i=0; i<interfaces.size(); i++) 
		{
		
			if(type.name()=="add")	// ADD rules
			{
				
				if (this.bandwidth != null && this.property != null)
				{
					command = ( new Tc(Element.qdisc, Command.add, interfaces.get(i).getName(), 
							Hierarchy.root, "1:0", null,this.property, this.bandwidth) ).getCompleteCommand();
				}
				else if (this.bandwidth != null)
				{
					command = (new Tc(Element.qdisc, Command.add, interfaces.get(i).getName(),
							Hierarchy.root, "1:0", null,null, this.bandwidth) ).getCompleteCommand();
				}
				else if(this.property != null)
				{
					command =  (new Tc(Element.qdisc, Command.add, interfaces.get(i).getName(),
							Hierarchy.root, "1:0", null,this.property, null)).getCompleteCommand();
				}
				else{
					log.warn("Impossible to add Traffic Control Rules on Hub: " +
							this.getName() + "...neither Bandwidth nor Property is set!");
				}
				
			}else if (type.name()=="del")	//DEL rules
			{
				if (this.bandwidth == null && this.property == null)
				{
					log.warn("Impossible delete Traffic Control Rules on Hub: " +
							this.getName() + "...neither Bandwidth nor Property is set!");
				
				}else 
					command = ( new Tc(Element.qdisc, Command.del, interfaces.get(i).getName(),
								Hierarchy.root,"", null,null,null) ).getCompleteCommand() ;
				
			}else if (type.name()=="replace")	//REPLACE rules
			{
				if (this.bandwidth != null && this.property != null)
				{
					command = ( new Tc(Element.qdisc, Command.replace, interfaces.get(i).getName(),
							Hierarchy.root,"1:0",null, this.property, this.bandwidth) ).getCompleteCommand();
				}else if (this.bandwidth != null)
					command = (new Tc(Element.qdisc, Command.replace, interfaces.get(i).getName(),
							Hierarchy.root, "1:0", null,null, this.bandwidth) ).getCompleteCommand();
				else if(this.property != null)
					command =  (new Tc(Element.qdisc, Command.replace, interfaces.get(i).getName(),
							Hierarchy.root, "1:0", null,this.property, null)).getCompleteCommand();
				else{
					log.warn("Impossible to add Traffic Control Rules on Hub: " +
							this.getName() + "...neither Bandwidth nor Property is set!");
				}
			}
			if(command!=""){
				distributedCommands.add(new DistributedCommand(command,
						interfaces.get(i).getParent().getHoster()));
			}
			
		}
		return distributedCommands;
	}
	
	/**
	 * @return the properties
	 */
	public Property getProperty() {
		return property;
	}

	/**
	 * @param properties
	 *            the properties to set
	 */
	public void setProperty(Property property) {
		this.property = property;
	}
	
	/**
	 * @param properties
	 *            the properties to remove
	 */
	public void removeProperty() {
		this.property = null;
	}
	
	/**
	 * @param {@link Bandwidth}
	 *            the andwidth to set
	 */
	public void setBandwidth(Bandwidth bandwidth){
		this.bandwidth = bandwidth;
	}
	
	/**
	 * @return {@link Bandwidth}
	 */
	public Bandwidth getBandwidth(){
		return bandwidth;
	}
}
